//
// Created by makui on 2024/12/10.
//

#ifndef SUPERVISOR_STORAGE_HPP
#define SUPERVISOR_STORAGE_HPP

#include "mysqlx/xdevapi.h"
#include <string>

#include "concepts.hpp"

#define DATABASE_IP "localhost"
#define DATABASE_USER "root"
#define DATABASE_PORT 33060
#define DATABASE_NAME "supervisor"
#define DATABASE_PASSWORD "123456"

namespace common {

// TODO 对增删改查过程中的异常进行捕获处理并生成日志
class Storage {
    inline static std::unique_ptr<mysqlx::Session> session_ = nullptr;
    inline static std::mutex mutex_;
    mysqlx::Schema db_;

    logger_t logger = common::Logger::logger("Storage");

    typedef const std::vector<std::pair<std::string, mysqlx::Value>> bind_t;

    static mysqlx::Session& session() {
        std::lock_guard lock(mutex_);
        if (!session_) {
            // Create a new session if it doesn't exist
            session_ = std::make_unique<mysqlx::Session>(
                DATABASE_IP, DATABASE_PORT, DATABASE_USER, DATABASE_PASSWORD
            );
        }
        return *session_;
    }
public:
    Storage() : db_(session().getSchema(DATABASE_NAME, true)) {}

    explicit Storage(const std::string &db_name) : db_(session().getSchema(db_name, true)) { }

    template <Entity entity_t>
    std::vector<entity_t> select(const std::string &from, const std::string &con = std::string(), bind_t &bind = {}) {
        auto table = db_.getTable(from, true);
        std::vector<entity_t> res;
        mysqlx::RowResult rows;
        if (con.empty()) {
            rows = table.select().execute();
        } else {
            rows = bind.empty() ? table.select().where(con).execute() : table.select().where(con).bind(bind).execute();
        }
        for (auto row : rows) {
            entity_t entity;
            entity.from_database(row);
            res.push_back(entity);
        }

        return res;
    }

    mysqlx::RowResult select(const std::string &from, const std::string &cols = "*", const std::string &con = std::string(), bind_t &bind = {}) {
        std::cout << db_.getName() << std::endl;
        auto table = db_.getTable(from, true);
        mysqlx::RowResult rows;
        if (con.empty()) {
            rows = table.select().execute();
        } else {
            rows = bind.empty() ? table.select().where(con).execute() : table.select().where(con).bind(bind).execute();
        }

        return rows;
    }

    template <Entity entity_t>
    void insert(const std::string &into, std::vector<entity_t> to_insert) {
        auto table = db_.getTable(into, true);
        for (auto insert : to_insert)
            insert.to_database(table);
    }

    void insert(const std::string &into, const std::vector<mysqlx::Value>& values, const std::vector<std::string> &cols) {
        auto table = db_.getTable(into, true);
        if (!cols.empty() && cols.size() != values.size()) throw std::runtime_error("Insert more columns");
        cols.empty() ? table.insert().values(values).execute() : table.insert(cols).values(values).execute();
    }

    void remove(const std::string &from, const std::string &con, bind_t &bind = {}) {
        if (con.empty()) return;
        auto table = db_.getTable(from, true);
        bind.empty() ? table.remove().where(con).execute() : table.remove().where(con).bind(bind).execute();
    }

    void update(const std::string& table_name, const std::vector<std::pair<std::string, mysqlx::Value>> &to_set, const std::string &con, bind_t &bind = {}) {
        auto table = db_.getTable(table_name, true);
        auto updater = table.update();
        for (const auto&[field, value] : to_set) {
            updater.set(field, value);
        }
        bind.empty() ? updater.where(con).execute() : updater.where(con).bind(bind).execute();
    }

    void transaction(const std::function<void()> &operations) const {
        session_->startTransaction();
        try {
            operations();
            session_->commit();
        } catch (const mysqlx::Error &e) {
            logger->error( e.what());
            session_->rollback();
        }
    }

};

}

#endif //SUPERVISOR_STORAGE_HPP
